package openglsuperbible.Chapter7;

import de.matthiasmann.twl.utils.PNGDecoder;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import openglsuperbible.glutils.*;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;

/**
 *
 * @author Matthew
 */
public class TextureArrays {
  public static final int DISPLAY_HEIGHT = 600;
  public static final int DISPLAY_WIDTH = 800;
   
  private GLFrame cameraFrame;
  private GLFrustrum frustrum;
  private MatrixStack projectionMatrix;
  private MatrixStack modelViewMatrix;
  private GeometryTransform transformPipeline; // Geometry Transform Pipeline

  private GLShader flatshader;
  private Shader starFieldShader;
  private Shader moonShader;
         /** time at last frame */
  private long lastFrame;

   private float[] mCamera ;
  
    private final int SMALL_STARS     = 100;
    private final int MEDIUM_STARS    = 40;
    private final int LARGE_STARS     = 15;
    
    private GLBatch smallStarBatch;
    private GLBatch mediumStarBatch;
    private GLBatch largeStarBatch;
    private GLBatch mountainRangeBatch;
    private GLBatch moonBatch;
    
    private int moonTexture;
    private int starTexture;
    
    private float vWhite [] = { 1.0f, 1.0f, 1.0f, 1.0f };
    
    
  public void render() {      
      
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    

    
    modelViewMatrix.push();        
    mCamera = cameraFrame.getCameraMatrix();
    modelViewMatrix.multiply4(mCamera); 
    
    glBindTexture(GL_TEXTURE_2D,starTexture);
    starFieldShader.use();
    starFieldShader.setUniformMatrix4("mvpMatrix", false, transformPipeline.getModelViewProjectionMatrix());
    
    starFieldShader.setUniform1i("starImage", 0);
        // Draw small stars
        glPointSize(4.0f);
        smallStarBatch.draw(starFieldShader.getAttributeLocations());

         //Draw medium sized stars
        glPointSize(8.0f);
        mediumStarBatch.draw(starFieldShader.getAttributeLocations());

        // Draw largest stars
        glPointSize(12.0f);
        largeStarBatch.draw(starFieldShader.getAttributeLocations());
        starFieldShader.endUse();
        
        flatshader.useShader();
        flatshader.setUniformMatrix4("mvpMatrix", false, transformPipeline.getModelViewProjectionMatrix());
        flatshader.setUniform4("vColor", vWhite);
        
        
//         Draw distant horizon
        glLineWidth(3.5f);        

        mountainRangeBatch.draw(flatshader.getAttributeLocations());
//        flatshader.endUse();
        
        //Draw the "moon" 
        glBindTexture(GL_TEXTURE_2D_ARRAY,moonTexture);
        moonShader.use();       
        moonShader.setUniformMatrix4("mvpMatrix", false, transformPipeline.getModelViewProjectionMatrix());
        moonShader.setUniform1i("moonImage", 0);
        
       // fTime goes from 0.0 to 28.0 and recycles
	float fTime = getTime()/1000;
	fTime = fmod(fTime, 28.0f);
        moonShader.setUniform1f("fTime", fTime);
        moonBatch.draw(moonShader.getAttributeLocations());
         glBindTexture(GL_TEXTURE_2D_ARRAY,moonTexture);
        modelViewMatrix.pop(); 
    Display.update();
  }
  
  
    
  public void initGL() {
    //2D Initialization
      
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);
    glEnable(GL_LINE_SMOOTH);
    glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    
    glEnable(GL_POINT_SPRITE);

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f );

    flatshader = GLShaderFactory.getFlatShader();
    moonShader = new Shader("MoonShader","MoonShader");
    starFieldShader = new Shader("StarField","StarField");
    
    starTexture = loadTexture("star",GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
    moonTexture = loadTexture2DArray("moon",29,true);
    modelViewMatrix = new MatrixStack();
    frustrum = new GLFrustrum();
    cameraFrame = new GLFrame();

    GLBatchFactory batch = new GLBatchFactory();
        float[] vVerts = {0,0,0}; 

        for(int i = 0; i < SMALL_STARS; i++)
        {
            vVerts[0] = (float)Math.random()* DISPLAY_WIDTH;
            vVerts[1] = (float)(Math.random() * DISPLAY_HEIGHT-100)+200;
            vVerts[2] = 0.0f; 
            
            batch.addVertex(vVerts, null, null);         
            
        }     
    
    smallStarBatch = batch.buildBatch(GL_POINTS);
    batch.clear();
    
         for(int i = 0; i < MEDIUM_STARS; i++)
        {
            vVerts[0] = (float)Math.random()* DISPLAY_WIDTH;
            vVerts[1] = (float)(Math.random() * DISPLAY_HEIGHT-100)+200;
            vVerts[2] = 0.0f; 
            
            batch.addVertex(vVerts,  null, null);         
            
        } 
    mediumStarBatch = batch.buildBatch(GL_POINTS);
    batch.clear();
    
        for(int i = 0; i < LARGE_STARS; i++)
        {
            vVerts[0] = (float)Math.random()* DISPLAY_WIDTH;
            vVerts[1] = (float)(Math.random() * DISPLAY_HEIGHT-100)+200;
            vVerts[2] = 0.0f; 
            
            batch.addVertex(vVerts,  null, null);         
            
        } 
    largeStarBatch = batch.buildBatch(GL_POINTS); 
    batch.clear();
    
    
       float[] vMountains = {    
                                 0.0f,  25.0f,
                                 50.0f, 100.0f,
                                100.0f, 25.0f,
                                225.0f, 125.0f,
                                300.0f, 50.0f,
                                375.0f, 100.0f,
                                460.0f, 25.0f,
                                525.0f, 100.0f,
                                600.0f, 20.0f,
                                675.0f, 70.0f,
                                750.0f, 25.0f,
                                800.0f, 90.0f 
        };        

       
        for(int i = 0; i < vMountains.length; i+=2)
        {
            vVerts[0] = vMountains[i];
            vVerts[1] = vMountains[i+1];
            vVerts[2] = 0.0f;           
            batch.addVertex(vVerts,null,null);
        } 
//     mountainRangeBatch = new SimpleGLBatch(GL_LINE_STRIP,vMountains,inx); 
    mountainRangeBatch = batch.buildBatch(GL_LINE_STRIP); 
    batch.clear();  
    
    
     // The Moon
    float x = 700.0f;     // Location and radius of moon
    float y = 500.0f;
    float r = 50.0f;
   // Another looping variable
    
    batch.addVertex(new float[]{x - r, y - r, 0.0f},null,new float[]{0.0f, 0.0f});
    batch.addVertex(new float[]{x + r, y - r, 0.0f},null,new float[]{1.0f, 0.0f});
    batch.addVertex(new float[]{x + r, y + r, 0.0f},null,new float[]{1.0f, 1.0f});
    batch.addVertex(new float[]{x - r, y + r, 0.0f},null,new float[]{0.0f, 1.0f});
    
    
    
    moonBatch = batch.buildBatch(GL_TRIANGLE_FAN); 
  }

  
  
  public void resizeGL() {
    //2D Scene
    glViewport(0,0,DISPLAY_WIDTH,DISPLAY_HEIGHT);
    frustrum.setOrthographic(0.0f, Display.getWidth(), 0.0f, Display.getHeight(), -1.0f, 1.0f);    
    projectionMatrix = new MatrixStack(frustrum.getProjectionMatrix());
    transformPipeline = new GeometryTransform(modelViewMatrix, projectionMatrix);
    
  }
      

  
      //main loop
    public void run() {
    while(!Display.isCloseRequested() && !Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {

        render();
  
      //Display.update();
      Display.sync(60);
    }
  }  

      
  public void destroy() {
    //Methods already check if created before destroying.
     Display.destroy();
  }
   
    public static void main(String[] args) {
    TextureArrays main = null;
    try {
      main = new TextureArrays();
      main.create();
      main.run();
    }
    catch(Exception ex) {
      System.out.println(ex.getMessage());
    }
    finally {
      if(main != null) {
        main.destroy();
      }
    }
  }
    
    public void create() throws LWJGLException {
    //Display
    Display.setDisplayMode(new DisplayMode(DISPLAY_WIDTH,DISPLAY_HEIGHT));
    Display.setFullscreen(true);
    Display.setTitle("Smoothing");
    Display.create();

    //OpenGL
    initGL();
    resizeGL();
  }
    
                                	/** 
	 * Calculate how many milliseconds have passed 
	 * since last frame.
	 * 
	 * @return milliseconds passed since last frame 
	 */
	public int getDelta() {
	    long time = getTime();
	    int delta = (int) (time - lastFrame);
	    lastFrame = time;
	 
	    return delta;
	}
	
	/**
	 * Get the accurate system time
	 * 
	 * @return The system time in milliseconds
	 */
	public long getTime() {
	    return (Sys.getTime() * 1000) / Sys.getTimerResolution();
	}
        
                /**
         * goes from 0.0 to 999.0 and recycles
         * @return 
         */
        public float fTime()
        {
            return fmod(getTime() /10.0f, 999.0f);
        }


    /**
    * Returns the floating point remainder
    * @param numerator
    * @param denominator
    * @return 
    */
    private float fmod(float numerator, float denominator) {
        return numerator % denominator;
    }
    
    
    private int loadTexture(String filename, int minFilter, int magFilter, int wrapMode){
            
            InputStream in;
            ByteBuffer buffer;
            int texID = glGenTextures();
            try {
                in = new FileInputStream("textures/" + filename + ".png");
                PNGDecoder decoder = new PNGDecoder(in);
                int width = decoder.getWidth();
                int height = decoder.getHeight();
                
                buffer = BufferUtils.createByteBuffer(4 * width * height);
                decoder.decode(buffer, width * 4, PNGDecoder.Format.RGBA);          
                buffer.flip();
                in.close();
                
                glBindTexture(GL_TEXTURE_2D, texID);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
                
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
                
                glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
                glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

//                             
                if(minFilter == GL_LINEAR_MIPMAP_LINEAR || 
                    minFilter == GL_LINEAR_MIPMAP_NEAREST ||
                     minFilter == GL_NEAREST_MIPMAP_LINEAR ||
                     minFilter == GL_NEAREST_MIPMAP_NEAREST){
                glGenerateMipmap(GL_TEXTURE_2D);}       
                glBindTexture(GL_TEXTURE_2D, 0);
                
            } catch (FileNotFoundException e) {
                System.err.println("Failed to find the texture file: " + filename + ".png");
                
            } catch (IOException ex) {
                System.err.println("Error reading file: " + filename + ".png");
                
            } 
           
            return texID;
        }
    
    
    private int loadTexture2DArray(String filename,int number,boolean isflipped){
           
                ImageDataPNG image = new ImageDataPNG("textures/texture_arrays/" + filename+"0",isflipped);
   
                int width = image.getWidth();
                int height = image.getHeight();
                           
                int  textureID = glGenTextures();
                
                glBindTexture(GL_TEXTURE_2D_ARRAY, textureID);
                glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
                
                glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                
                glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
                glTexImage3D(GL_TEXTURE_2D_ARRAY, 0,GL_RGBA, width, height, number,0, GL_RGBA, GL_UNSIGNED_BYTE,(java.nio.ByteBuffer) null);
     
                for(int i = 0; i < number; i++) {
		
                    String tempfname = filename + i;
	
		image = new ImageDataPNG("textures/texture_arrays/" +tempfname,isflipped);

		glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, image.getBuffer());

		} 
                glGenerateMipmap(GL_TEXTURE_2D_ARRAY);      
                glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
            return textureID;
        }
    
    
    /**
     * Inner class to handle image loading.
     */
     private class ImageDataPNG{
            private int width;
            private int height;
            private ByteBuffer buffer;
         
        public ImageDataPNG(String filename,boolean isflipped) {


            InputStream in;

                try {
                    in = new FileInputStream(filename + ".png");
                    PNGDecoder decoder = new PNGDecoder(in);
                    width = decoder.getWidth();
                    height = decoder.getHeight();

                    buffer = BufferUtils.createByteBuffer(4 * width * height);
                    if (!isflipped){
                        decoder.decode(buffer, width * 4, PNGDecoder.Format.RGBA);
                    }
                    else
                    {
                        decoder.decodeFlipped(buffer, width * 4, PNGDecoder.Format.RGBA);
                    }
                    buffer.flip();
                    in.close();
                    } catch (FileNotFoundException ex) {
                    System.err.println("Failed to find the texture file: " + filename + ".png");

                    } catch (IOException ex) {
                        System.err.println("Error reading file: " + filename + ".png");

                    }   
        }
        
            public ByteBuffer getBuffer() {
                return buffer;
            }

            public int getHeight() {
                return height;
            }

            public int getWidth() {
                return width;
            }
     }
}
